# -*-* encoding:UTF-8 -*-
# author            : mengy
# date              : 2018/12/19
# python-version    : Python 3.7.0
# description       : 五子棋:人机对战--初级

from time import sleep
import pygame
from pygame.locals import *
from random import randint

LEVEL = 15
GRADE = 10
MAX = 1008611

STATU_ERROR = '999'
STATU_SUCCESS = '000'
STATU_BLACK = '001'
STATU_WHITE = '002'

BACKGROUND = (201, 202, 187)
CHECKERBOARD = (80, 80, 80)
BUTTON = (52, 53, 44)

WHITE_IMAGE = 'res/images/White_chess.gif'
BLACK_IMAGE = 'res/images/Black_chess.gif'


def scan(chesspanel, color):
    """
    分析棋盘局势，并在每个空位，做出胜算评估 \n
    :param chesspanel:    棋盘 \n
    :param color:   棋子颜色 \n
    :return:    每个点四个方向以及自身的胜算评估结果
    """
    shape = [[[0 for high in range(5)] for col in range(15)] for row in range(15)]
    # 扫描每一个点，然后在空白的点每一个方向上做出价值评估！！
    for i in range(15):
        for j in range(15):
            # 如果此处为空 那么就可以开始扫描周边
            if chesspanel[i][j] == 0:
                m = i
                n = j
                # 如果上方跟当前传入的颜色参数一致，那么加分到[0]位！
                while n - 1 >= 0 and chesspanel[m][n - 1] == color:
                    n -= 1
                    shape[i][j][0] += GRADE
                    if n - 1 >= 0 and chesspanel[m][n - 1] == 0:
                        shape[i][j][0] += 1
                    if n - 1 >= 0 and chesspanel[m][n - 1] == -color:
                        shape[i][j][0] -= 2
                m = i
                n = j
                # 如果下方跟当前传入的颜色参数一致，那么加分到[0]位！
                while (n + 1 < LEVEL and chesspanel[m][n + 1] == color):
                    n += 1
                    shape[i][j][0] += GRADE
                    if n + 1 < LEVEL and chesspanel[m][n + 1] == 0:
                        shape[i][j][0] += 1
                    if n + 1 < LEVEL and chesspanel[m][n + 1] == -color:
                        shape[i][j][0] -= 2
                m = i
                n = j
                # 如果左边跟当前传入的颜色参数一致，那么加分到[1]位！
                while (m - 1 >= 0 and chesspanel[m - 1][n] == color):
                    m -= 1
                    shape[i][j][1] += GRADE
                    if m - 1 >= 0 and chesspanel[m - 1][n] == 0:
                        shape[i][j][1] += 1
                    if m - 1 >= 0 and chesspanel[m - 1][n] == -color:
                        shape[i][j][1] -= 2
                m = i
                n = j
                # 如果右边跟当前传入的颜色参数一致，那么加分到[1]位！
                while (m + 1 < LEVEL and chesspanel[m + 1][n] == color):
                    m += 1
                    shape[i][j][1] += GRADE
                    if m + 1 < LEVEL and chesspanel[m + 1][n] == 0:
                        shape[i][j][1] += 1
                    if m + 1 < LEVEL and chesspanel[m + 1][n] == -color:
                        shape[i][j][1] -= 2
                m = i
                n = j
                # 如果左下方跟当前传入的颜色参数一致，那么加分到[2]位！
                while (m - 1 >= 0 and n + 1 < LEVEL and chesspanel[m - 1][n + 1] == color):
                    m -= 1
                    n += 1
                    shape[i][j][2] += GRADE
                    if m - 1 >= 0 and n + 1 < LEVEL and chesspanel[m - 1][n + 1] == 0:
                        shape[i][j][2] += 1
                    if m - 1 >= 0 and n + 1 < LEVEL and chesspanel[m - 1][n + 1] == -color:
                        shape[i][j][2] -= 2
                m = i
                n = j
                # 如果右上方跟当前传入的颜色参数一致，那么加分到[2]位！
                while (m + 1 < LEVEL and n - 1 >= 0 and chesspanel[m + 1][n - 1] == color):
                    m += 1
                    n -= 1
                    shape[i][j][2] += GRADE
                    if m + 1 < LEVEL and n - 1 >= 0 and chesspanel[m + 1][n - 1] == 0:
                        shape[i][j][2] += 1
                    if m + 1 < LEVEL and n - 1 >= 0 and chesspanel[m + 1][n - 1] == -color:
                        shape[i][j][2] -= 2
                m = i
                n = j
                # 如果左上方跟当前传入的颜色参数一致，那么加分到[3]位！
                while (m - 1 >= 0 and n - 1 >= 0 and chesspanel[m - 1][n - 1] == color):
                    m -= 1
                    n -= 1
                    shape[i][j][3] += GRADE
                    if m - 1 >= 0 and n - 1 >= 0 and chesspanel[m - 1][n - 1] == 0:
                        shape[i][j][3] += 1
                    if m - 1 >= 0 and n - 1 >= 0 and chesspanel[m - 1][n - 1] == -color:
                        shape[i][j][3] -= 2
                m = i
                n = j
                # 如果右下方跟当前传入的颜色参数一致，那么加分到[3]位！
                while m + 1 < LEVEL and n + 1 < LEVEL and chesspanel[m + 1][n + 1] == color:
                    m += 1
                    n += 1
                    shape[i][j][3] += GRADE
                    if m + 1 < LEVEL and n + 1 < LEVEL and chesspanel[m + 1][n + 1] == 0:
                        shape[i][j][3] += 1
                    if m + 1 < LEVEL and n + 1 < LEVEL and chesspanel[m + 1][n + 1] == -color:
                        shape[i][j][3] -= 2
    return shape


def sort(shape):
    """
    对胜算评估分数进行降序排序 \n
    :param shape:   每个空位的胜算评估分数三元数组 \n
    :return:    排序后的胜算评估分数三元数组
    """
    # print([item for item in shape])
    for i in shape:
        for j in i:
            for x in range(5):
                for w in range(3, x - 1, -1):
                    if j[w - 1] < j[w]:
                        temp = j[w]
                        j[w - 1] = j[w]
                        j[w] = temp
    # print("此次排序完成!")
    return shape


def evaluate(shape):
    """
    评估出最合适的落子位置 \n
    :param shape:   胜算评估分数 \n
    :return:    胜算最大的落子位置的坐标值以及最大分数 \n
         max_x:    落子位置的横坐标 \n
         max_y:    落子位置的纵坐标 \n
         max:      落子位置的胜算评估分数
    """
    for i in range(LEVEL):
        for j in range(LEVEL):
            if shape[i][j][0] == 4:
                return i, j, MAX
    shape[i][j][4] = shape[i][j][0] * 1000 + shape[i][j][1] * 100 + shape[i][j][2] * 10 + shape[i][j][3]
    max_x = 0
    max_y = 0
    max = 0
    for i in range(15):
        for j in range(15):
            if max < shape[i][j][4]:
                max = shape[i][j][4]
                max_x = i
                max_y = j
    print("落子在 ( " + str(max_x) + " , " + str(max_y) + " )处胜算最大，胜率分为:" + str(max))
    return max_x, max_y, max


class Chess(object):
    """
    棋盘类
    """

    def __init__(self):
        self.chess_pane = [[0 for high in range(15)] for col in range(15)]

    def fall(self, x, y, color):
        """
        落子方法\n
        :param x:   落子的横坐标 \n
        :param y:   落子的纵坐标 \n
        :param color:   当前落子的颜色 \n
        :return:
            STATU_ERROR     :   落子失败 \n
            STATU_SUCCESS   :   落子成功 \n
            STATU_WHITE     :   白子胜 \n
            STATU_BLACK     :   黑子胜 \n
        """
        if (x < 0 or x > LEVEL - 1 or y < 0 or y > LEVEL - 1):
            return STATU_ERROR
        # print(['白棋下' if color > 1 else '黑棋下'])
        self.chess_pane[x][y] = color
        if is_win(x, y, color, self.chess_pane, 4):
            if color < 0:
                print("白棋胜!!")
                return STATU_WHITE
            else:
                print("黑棋胜!!")
                return STATU_BLACK
        return STATU_SUCCESS

    def is_empty(self, m, n):
        return self.chess_pane[m][n] == 0

    # def clear(self):



def is_win(x, y, color, chesspanel, length):
    """
    判断是否满足同颜色的棋子连成线 \n
    :param x:   横坐标 \n
    :param y:   纵坐标 \n
    :param color:   棋子颜色 \n
    :param chesspanel:  棋盘 \n
    :param length:  成线棋子的数量 \n
    :return:    是否成线 \n
    """
    count1, count2, count3, count4 = 0, 0, 0, 0
    # 横向判断<左边>
    i = x - 1
    while (i >= 0):
        if color == chesspanel[i][y]:
            count1 += 1
            i -= 1
        else:
            break
    # 横向判断<右边>
    i = x + 1
    while i < LEVEL:
        if chesspanel[i][y] == color:
            count1 += 1
            i += 1
        else:
            break
    # 纵向判断<上边>
    j = y - 1
    while (j >= 0):
        if chesspanel[x][j] == color:
            count2 += 1
            j -= 1
        else:
            break
    # 纵向判断<下边>
    j = y + 1
    while j < LEVEL:
        if chesspanel[x][j] == color:
            count2 += 1
            j += 1
        else:
            break
    # 正对角线判断<左上>
    i, j = x - 1, y - 1
    while (i >= 0 and j >= 0):
        if chesspanel[i][j] == color:
            count3 += 1
            i -= 1
            j -= 1
        else:
            break
    # 正对角线判断<右下>
    i, j = x + 1, y + 1
    while (i < LEVEL and j < LEVEL):
        if chesspanel[i][j] == color:
            count3 += 1
            i += 1
            j += 1
        else:
            break
    # 反对角线判断<右上>
    i, j = x + 1, y - 1
    while (i < LEVEL and j >= 0):
        if chesspanel[i][j] == color:
            count4 += 1
            i += 1
            j -= 1
        else:
            break
    # 反对角线判断<左下>
    i, j = x - 1, y + 1
    while (i > 0 and j < LEVEL):
        if chesspanel[i][j] == color:
            count4 += 1
            i -= 1
            j += 1
        else:
            break
    if count1 >= length or count2 >= length or count3 >= length or count4 >= length:
        return True
    else:
        return False


def auto_fall(chess, m, n):
    """
    随机落子在周边 \n
    :param chess: 棋盘 \n
    :param m: 横坐标 \n
    :param n: 纵坐标 \n
    :return: 落子的坐标 \n
    """
    a1 = [1, -1, 1, -1, 1, -1, 0, 0]
    b1 = [1, -1, -1, 1, 0, 0, 1, -1]
    rand = randint(0, 7)
    while m + a1[rand] >= 0 and m + a1[rand] < LEVEL and n + b1[rand] >= 0 and n + b1[rand] < LEVEL and \
            chess[m + a1[rand]][n + b1[rand]] != 0:
        rand = randint(0, 7)
    return m + a1[rand], n + b1[rand]


def beta_go(chesspanel, m, n, color, times):
    """
    机器计算下棋的位置 \n
    :param chesspanel: 棋盘 \n
    :param m: 横坐标 \n
    :param n: 纵坐标 \n
    :param color: 棋子颜色 \n
    :param times: 落子次数 \n
    :return: 最佳落子位置 \n
    """
    if times < 2:
        return auto_fall(chesspanel, m, n)
    else:
        # 分析对方局势
        shape_P = scan(chesspanel, -color)
    # 分析己方局势
    shape_C = scan(chesspanel, color)
    shape_P = sort(shape_P)
    shape_C = sort(shape_C)
    max_x_P, max_y_P, max_P = evaluate(shape_P)
    max_x_C, max_y_C, max_C = evaluate(shape_C)
    if max_P > max_C and max_C < MAX:
        return max_x_P, max_y_P
    else:
        return max_x_C, max_y_C


def render_black():
    """
    渲染黑棋 \n
    :return:  黑棋
    """
    black = pygame.image.load(BLACK_IMAGE).convert_alpha()
    black = pygame.transform.smoothscale(black, (int(black.get_width()), int(black.get_height())))
    return black


def render_white():
    """
    渲染白棋 \n
    :return:  白棋
    """
    white = pygame.image.load(WHITE_IMAGE).convert_alpha()
    white = pygame.transform.smoothscale(white, (int(white.get_width()), int(white.get_height())))
    return white


def render_screen():
    pygame.init()
    screen = pygame.display.set_mode((800, 600), 0, 32)
    pygame.display.set_caption("五子棋 - 人机对战（初级）")
    screen.fill(BACKGROUND)

    # 画棋盘网格线 15×15
    for i in range(15):
        pygame.draw.line(screen, CHECKERBOARD, (40 * i + 20, 20), (40 * i + 20, 580))
        pygame.draw.line(screen, CHECKERBOARD, (20, 40 * i + 20), (580, 40 * i + 20))
    # 画边线
    pygame.draw.line(screen, CHECKERBOARD, (20, 20), (580, 20), 5)
    pygame.draw.line(screen, CHECKERBOARD, (20, 20), (20, 580), 5)
    pygame.draw.line(screen, CHECKERBOARD, (580, 20), (580, 580), 5)
    pygame.draw.line(screen, CHECKERBOARD, (20, 580), (580, 580), 5)

    # 画定位点
    pygame.draw.circle(screen, CHECKERBOARD, (140, 140), 6)
    pygame.draw.circle(screen, CHECKERBOARD, (140, 460), 6)
    pygame.draw.circle(screen, CHECKERBOARD, (460, 140), 6)
    pygame.draw.circle(screen, CHECKERBOARD, (460, 460), 6)
    pygame.draw.circle(screen, CHECKERBOARD, (300, 300), 6)
    font = pygame.font.SysFont("黑体", 22)

    # 画‘重新开始’和‘退出游戏’按钮
    pygame.draw.rect(screen, (255, 255, 255), [628, 198, 124, 50], 5)
    pygame.draw.rect(screen, BUTTON, [628, 298, 124, 50], 5)
    s_font = pygame.font.Font('res/fonts/font.ttf', 30)
    text1 = s_font.render("重新开始", True, BUTTON)
    text2 = s_font.render("退出游戏", True, BUTTON)
    screen.blit(text1, (630, 200))
    screen.blit(text2, (630, 300))
    # 更新显示到屏幕表面,如果不加，窗口刚打开时是黑的
    pygame.display.flip()
    # 过滤事件
    pygame.event.set_blocked([1, 4, KEYUP, JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN, JOYBUTTONUP, JOYHATMOTION])
    pygame.event.set_allowed([MOUSEBUTTONDOWN, MOUSEBUTTONUP, 12, KEYDOWN])
    return screen


def run(chesspanel):
    """
    开始绘制棋盘 \n
    :param chesspanel: 棋盘 \n
    :return:
    """
    screen = render_screen()
    white = render_white()
    black = render_black()
    dot_list = [(20 + i * 40 - white.get_width() / 2, 20 + j * 40 - white.get_height() / 2) for i in range(LEVEL) for
                j in range(LEVEL)]
    color = -1
    times = 1
    flag = False
    gameover = False
    clock = pygame.time.Clock()
    while not flag:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                exit()
            elif event.type == MOUSEBUTTONDOWN:
                """
                event.button 值说明:
                    1 -- 左击
                    2 -- 滚轮点击
                    3 -- 右击
                    4 -- 滚轮前滑
                    5 -- 滚轮后滑
                """
                if event.button == 1:
                    x, y = pygame.mouse.get_pos()
                    # print('鼠标点击的坐标(%s,%s)' % (x, y))
                    if 20 <= x <= 580 and 20 <= y <= 580 and ((x - 20) % 40 <= LEVEL or (x - 20) % 40 >= 0) and (
                            (y - 20) % 40 <= LEVEL or (y - 20) % 40 >= 0) and not gameover:
                        # 人类落子
                        m = int(round((x - 20) / 40))
                        n = int(round((y - 20) / 40))
                        if not chesspanel.is_empty(m, n):
                            text("此处已存在棋子！", screen, 20)
                            continue
                        color = -1 * color
                        statu = chesspanel.fall(m, n, color)
                        screen.blit(black, dot_list[LEVEL * m + n])
                        pygame.display.flip()
                        if statu == STATU_BLACK:
                            text("游戏结束！人类胜！", screen, 20)
                            gameover = True
                            break
                        # 机器落子
                        color = -1 * color
                        sleep(0.1)
                        print("第%s次落子" % times)
                        x, y = beta_go(chesspanel.chess_pane, m, n, color, times)
                        times += 1
                        print("预测落子位置:(%s,%s)" % (str(x), str(y)))
                        statu = chesspanel.fall(x, y, color)
                        screen.blit(white, dot_list[LEVEL * x + y])
                        pygame.display.flip()
                        if statu == STATU_WHITE:
                            text("游戏结束！机器胜！", screen, 20)
                            gameover = True
                            break
                    if 630 <= x <= 750 and 200 <= y <= 250:
                        print("重新开始！")
                        gameover = False
                        newpanel = Chess()
                        run(newpanel)
                    elif 630 <= x <= 750 and 300 <= y <= 350:
                        print("退出游戏")
                        sleep(0.5)
                        pygame.quit()
                        exit()
        clock.tick(60)


# 绘制提示器（内容，屏幕，字大小）
def text(s, screen, x):
    # 先把上一次的内容用一个矩形覆盖
    pygame.draw.rect(screen, BACKGROUND, [610, 100, 180, 50])
    # 定义字体跟大小
    s_font = pygame.font.Font('res/fonts/font.ttf', x)
    # 定义内容，是否抗锯齿，颜色
    s_text = s_font.render(s, True, BUTTON)
    # 将字放在窗口指定位置
    screen.blit(s_text, (620, 100))
    pygame.display.flip()


now = Chess()
run(now)
